package com.learn.redisforspringboot.app.impl;

import com.learn.redisforspringboot.app.SecKillService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * @author PC
 * 秒杀实现类
 */
@Service
public class SecKillServiceImpl implements SecKillService {

    public final static Logger logger = LoggerFactory.getLogger(SecKillServiceImpl.class);

    public final static String SEC_KILL_SCRIPT = "local userid=KEYS[1];\r\n" +
            "local prodid=KEYS[2];\r\n" +
            "local qtkey='sk:'..prodid..\":qt\";\r\n" +
            "local usersKey='sk:'..prodid..\":usr\";\r\n" +
            "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
            "if tonumber(userExists)==1 then \r\n" +
            "   return 2;\r\n" +
            "end\r\n" +
            "local num= redis.call(\"get\" ,qtkey);\r\n" +
            "if tonumber(num)<=0 then \r\n" +
            "   return 0;\r\n" +
            "else \r\n" +
            "   redis.call(\"decr\",qtkey);\r\n" +
            "   redis.call(\"sadd\",usersKey,userid);\r\n" +
            "end\r\n" +
            "return 1";
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Boolean doSecKill(String userId, String prodId) {
        //1.判空处理
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(prodId)) {
            logger.info("userId or prodId is empty");
            return false;
        }
        //2.连接redis,由于使用的是Springboot,可以直接用RedisTemplate，此步省略

        //3.拼接key
        //3.1 库存key
        String inventoryKey = String.format("sk:%s:qt", prodId);
        //3.2 秒杀成功用户key
        String userKey = String.format("sk:%s:user", prodId);

        //开启事务
        redisTemplate.setEnableTransactionSupport(true);

        //7 秒杀过程
        //使用事务
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                //监视库存
                redisTemplate.watch(inventoryKey);

                //4 获取库存，如果库存为null，秒杀还没开始
                String inventoryNumStr = Optional.ofNullable(redisTemplate.opsForValue().get(inventoryKey)).orElse(StringUtils.EMPTY).toString();
                if (StringUtils.isEmpty(inventoryNumStr)){
                    logger.info("The Lightning Deal hasn't started yet");
                    return false;
                }
                //5 判断用户是否重复秒杀操作
                if(Optional.ofNullable(redisTemplate.opsForSet().isMember(userKey, userId)).orElse(false)){
                    //您已经秒杀成功，不能重复秒杀
                    logger.info("You have already had a Lightning Deal and cannot repeat it");
                    return false;
                }
                //6 判断如果商品库存数量<=0, 秒杀结束
                //6.1 如果商品数量不能转换为数字，需要联系活动举办方
                long inventoryNum;
                try{
                    inventoryNum = Long.parseLong(inventoryNumStr);
                }catch (Exception exception){
                    logger.info("There is an abnormality in the current flash sale event, please contact the event organizer");
                    return false;
                }
                if (inventoryNum <= 0L){
                    //活动结束
                    logger.info("The Lightning Deal has ended");
                    return false;
                }
                redisTemplate.multi();
                //7.1 库存-1
                redisTemplate.boundValueOps(inventoryKey).decrement(1);
                //7.2 把秒杀成功用户添加到清单中
                redisTemplate.opsForSet().add(userKey, userId);

                List<Object> objectList = redisTemplate.exec();
                if (CollectionUtils.isEmpty(objectList)){
                    //秒杀失败
                    logger.info("Lightning Deal failed");
                }else{
                    logger.info(userId + "get prod");
                }
                return objectList;
            }
        });
        return true;
    }

    @Override
    public Boolean doSecKillByScript(String userId, String prodId) {
        //构建LUA脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);//返回类型是Long
        redisScript.setScriptText(SEC_KILL_SCRIPT);
        //执行LUA脚本
        Long result = redisTemplate.execute(redisScript, Arrays.asList(userId, prodId), "");
        boolean killResult = false;
        if (Objects.isNull(result)){
            //返回值异常
            logger.info("There is an abnormality in the current flash sale event, please contact the event organizer");
        }else if (result == 0L) {
            //活动结束
            logger.info("The Lightning Deal has ended");
        } else if (result == 1L) {
            //抢购成功
            logger.info(userId + "get prod");
            killResult = true;
        } else if (result == 2L) {
            //您已经秒杀成功，不能重复秒杀
            logger.info("You have already had a Lightning Deal and cannot repeat it");
        } else {
            //其他情况，表示异常
            logger.info("There is an abnormality in the current flash sale event, please contact the event organizer");
        }
        return killResult;
    }
}
